package mockexchange

import (
	"fmt"
	"math"
	"strconv"
	"time"

	ex "gitee.com/378077287/exchanges"
	"github.com/boltdb/bolt"
	"github.com/golang/glog"
)

// MockExchange 模拟交易所对象.
type MockExchange struct {
	Symbol        string              //交易对
	OrderDic      map[string]ex.Order //订单薄
	Record        bool                //是否用日志记录交易记录
	Hold          float64             // 持仓
	CostPrice     float64             //持仓成本
	Profit        float64             //盈利
	TradeAmount   float64             //累计交易额
	TotalFee      float64             //累计手续费
	MarkerFeeRate float64             //挂单费率
	TakerFeeRate  float64             //吃单费率
	AskPrice      float64             //卖一价格
	BidPrice      float64             //买一价格
}

// buy 买入.
func (m *MockExchange) buy(price, amount float64, orderType string) {
	if m.Record {
		glog.Infof("buy %s price:%f, amount:%f", m.Symbol, price, amount)
	}
	// 如果是加仓
	if m.Hold >= 0 {
		m.CostPrice = (amount*price + m.CostPrice*math.Abs(m.Hold)) / (math.Abs(m.Hold) + amount)

	} else {
		// 减仓
		if math.Abs(m.Hold) >= amount {
			m.Profit += (m.CostPrice - price) * amount
		} else {
			m.Profit += (m.CostPrice - price) * math.Abs(m.Hold)
		}

	}
	m.Hold += amount
	m.TradeAmount += amount
	if orderType == "limit" {
		m.TotalFee += amount * price * m.MarkerFeeRate
		m.Profit -= amount * price * m.MarkerFeeRate
	} else {
		m.TotalFee += amount * price * m.TakerFeeRate
		m.Profit -= amount * price * m.TakerFeeRate
	}
	m.syncToDB()
}

// sell 卖出.
func (m *MockExchange) sell(price, amount float64, orderType string) {
	if m.Record {
		glog.Infof("sell %s price:%f, amount:%f", m.Symbol, price, amount)
	}
	// 加仓
	if m.Hold <= 0 {
		m.CostPrice = (amount*price + m.CostPrice*math.Abs(m.Hold)) / (math.Abs(m.Hold) + amount)
	} else {
		if math.Abs(m.Hold) >= amount {
			m.Profit += (price - m.CostPrice) * amount
		} else {
			m.Profit += (price - m.CostPrice) * math.Abs(m.Hold)
		}
	}
	m.Hold -= amount
	m.TradeAmount += amount
	if orderType == "limit" {
		m.TotalFee += amount * price * m.MarkerFeeRate
		m.Profit -= amount * price * m.MarkerFeeRate
	} else {
		m.TotalFee += amount * price * m.TakerFeeRate
		m.Profit -= amount * price * m.TakerFeeRate
	}
	m.syncToDB()
}

// MarketSell 市价卖.
func (m *MockExchange) MarketSell(amount float64) ex.Order {
	price := m.BidPrice

	m.sell(price, amount, "market")

	return ex.Order{
		ID:           m.genID(),
		Symbol:       m.Symbol,
		Time:         time.Now(),
		Price:        price,
		Amount:       amount,
		AvgPrice:     price,
		FilledAmount: amount,
		Direction:    ex.Sell,
		Type:         ex.OrderTypeMarket,
		UpdateTime:   time.Now(),
		Status:       ex.OrderStatusFilled,
	}
}

// MarketBuy 市价买.
func (m *MockExchange) MarketBuy(amount float64) ex.Order {
	price := m.AskPrice
	m.buy(price, amount, "market")

	return ex.Order{
		ID:           m.genID(),
		Symbol:       m.Symbol,
		Time:         time.Now(),
		Price:        price,
		Amount:       amount,
		AvgPrice:     price,
		FilledAmount: amount,
		Direction:    ex.Buy,
		Type:         ex.OrderTypeMarket,
		UpdateTime:   time.Now(),
		Status:       ex.OrderStatusFilled,
	}
}

// LimitSell 限价单卖.
func (m *MockExchange) LimitSell(price, amount float64) ex.Order {
	o := ex.Order{
		ID:         m.genID(),
		Symbol:     m.Symbol,
		Time:       time.Now(),
		Price:      price,
		Amount:     amount,
		AvgPrice:   price,
		Direction:  ex.Sell,
		Type:       ex.OrderTypeLimit,
		UpdateTime: time.Now(),
		Status:     ex.OrderStatusNew,
	}
	m.OrderDic[o.ID] = o
	return o
}

// LimitBuy 限价单买.
func (m *MockExchange) LimitBuy(price, amount float64) ex.Order {

	o := ex.Order{
		ID:         m.genID(),
		Symbol:     m.Symbol,
		Time:       time.Now(),
		Price:      price,
		Amount:     amount,
		AvgPrice:   price,
		Direction:  ex.Buy,
		Type:       ex.OrderTypeLimit,
		UpdateTime: time.Now(),
		Status:     ex.OrderStatusNew,
	}
	m.OrderDic[o.ID] = o
	return o
}

// CancelOrder 取消订单id.
func (m *MockExchange) CancelOrder(id string) error {
	_, had := m.OrderDic[id]
	if had {
		delete(m.OrderDic, id)
	} else {
		return fmt.Errorf("订单id:%s 不存在", id)
	}
	return nil
}

// AmendOrder 修改订单.
func (m *MockExchange) AmendOrder(id string, price, amount float64) (ex.Order, error) {
	order, had := m.OrderDic[id]
	if had {
		order.Price = price
		order.Amount = amount
		m.OrderDic[id] = order
		return order, nil
	}
	return ex.Order{}, fmt.Errorf("订单id:%s 不存在", id)
}

// OpenOrders 未成交的订单.
func (m *MockExchange) OpenOrders() []ex.Order {
	var orders []ex.Order
	for _, order := range m.OrderDic {
		orders = append(orders, order)
	}
	return orders
}

// genID 生成订单id.
func (m *MockExchange) genID() string {
	n := time.Now().UnixNano()
	return fmt.Sprintf("%d", n)
}

// Init 初始化.
func (m *MockExchange) Init() error {
	if m.OrderDic == nil {
		m.OrderDic = make(map[string]ex.Order)
	}
	m.syncFromDB()
	return nil
}

//QuoteUpdate 行情更新.
func (m *MockExchange) QuoteUpdate(askPrice, bidPrice float64) error {
	if askPrice == 0 || bidPrice == 0 {
		return fmt.Errorf("价格不能为0")
	}
	m.AskPrice = askPrice
	m.BidPrice = bidPrice
	var completeOrderID []string
	for _, o := range m.OrderDic {
		if o.Direction == ex.Buy && o.Price >= m.AskPrice {
			completeOrderID = append(completeOrderID, o.ID)
			m.buy(o.Price, o.Amount, "limit")
		}
		if o.Direction == ex.Sell && o.Price <= m.BidPrice {
			completeOrderID = append(completeOrderID, o.ID)
			m.sell(o.Price, o.Amount, "limit")
		}
	}
	for _, idString := range completeOrderID {
		delete(m.OrderDic, idString)
	}
	return nil
}

// syncToDB 同步信息到数据库.
func (m *MockExchange) syncToDB() error {
	dbName := "mockExchange.db"
	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		glog.Error(err)
	}
	defer db.Close()

	// 判断桶是否存在
	db.Update(func(tx *bolt.Tx) error {
		// 当返回错误时，修改操作是会被回滚
		//判断要创建的桶是否存在
		b := tx.Bucket([]byte("data"))
		if b == nil {

			//创建叫"MyBucket"的桶
			_, err := tx.CreateBucket([]byte("data"))
			if err != nil {
				//也可以在这里对桶做插入操作
				glog.Error(err)
				return err
			}
		}
		return nil
	})
	// 更新数据
	db.Update(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("data"))
		if b != nil {
			b.Put([]byte("Hold"), float64ToBytes(m.Hold))
			b.Put([]byte("CostPrice"), float64ToBytes(m.CostPrice))
			b.Put([]byte("Profit"), float64ToBytes(m.Profit))
			b.Put([]byte("TotalFee"), float64ToBytes(m.TotalFee))
			b.Put([]byte("TradeAmount"), float64ToBytes(m.TradeAmount))
		}
		return nil
	})
	return nil
}

// syncFromDB 从数据库同步数据到结构.
func (m *MockExchange) syncFromDB() error {
	dbName := "mockExchange.db"
	db, err := bolt.Open(dbName, 0600, nil)
	if err != nil {
		glog.Error(err)
	}
	defer db.Close()

	db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte("data"))
		if b != nil {
			m.Hold = bytesToFloat64(b.Get([]byte("Hold")))
			m.CostPrice = bytesToFloat64(b.Get([]byte("CostPrice")))
			m.Profit = bytesToFloat64(b.Get([]byte("Profit")))
			m.TotalFee = bytesToFloat64(b.Get([]byte("TotalFee")))
			m.TradeAmount = bytesToFloat64(b.Get([]byte("TradeAmount")))
		}
		return nil
	})
	return nil
}

// float64ToBytes 浮点数转字节.
func float64ToBytes(v float64) []byte {
	s := strconv.FormatFloat(v, 'f', -1, 64)
	return []byte(s)
}

// bytesToFloat64 字节转浮点数.
func bytesToFloat64(b []byte) float64 {
	v, err := strconv.ParseFloat(string(b), 64)
	if err != nil {
		glog.Error(err)
		return 0
	}
	return v
}
